#include <geo\osmio.h>
#include <geo\mercatorProjection.h>
#include <base\vec2d.h>
#include <trajectory\chainCoder.h>

#include <iostream>
#include <stdio.h>
#include <stdlib.h>

#include <cmath>

#define SWAP(value0, value1) {int value2 = value0; value0 = value1; value1 =value2;}

bool
osmio::processNode(xmlTextReaderPtr reader) {
  static int trksegno = -1;

  const char *cname = (const char *) xmlTextReaderConstName(reader);

  if (strcmp(cname, "trkpt") == 0) {
    int attrcnt = xmlTextReaderAttributeCount(reader);

    tracks[tracks.size() - 1].addCoordinate(
      geoLocation((const char *) xmlTextReaderGetAttribute(reader, (xmlChar *) "lat"),
                  (const char *) xmlTextReaderGetAttribute(reader, (xmlChar *) "lon")));
  }
  else if (strcmp(cname, "bounds") == 0) {
    north_west.set((const char *) xmlTextReaderGetAttribute(reader, (xmlChar *) "minlat"),
                   (const char *) xmlTextReaderGetAttribute(reader, (xmlChar *) "minlon"));
    south_east.set((const char *) xmlTextReaderGetAttribute(reader, (xmlChar *) "maxlat"),
                   (const char *) xmlTextReaderGetAttribute(reader, (xmlChar *) "maxlon"));
  }
  else if (strcmp(cname, "trkseg") == 0) {
    trksegno++;

    if (trksegno % 2 == 0) {
      tracks.push_back(track<geoLocation>(trksegno / 2));
    }
  }

  return true;
}

void
osmio::streamFile(const char *filename) {
    xmlTextReaderPtr reader;
    int ret;

    reader = xmlReaderForFile(filename, NULL, 0);
    if (reader != NULL) {
        ret = xmlTextReaderRead(reader);
        while (ret == 1) {
          if (!processNode(reader)) {
            ret = 0;
          }
          else 
          ret = xmlTextReaderRead(reader);
        }
        xmlFreeTextReader(reader);
        if (ret != 0) {
            fprintf(stderr, "%s : failed to parse\n", filename);
        }
    } else {
        fprintf(stderr, "Unable to open %s\n", filename);
    }
    std::cout << "tracks count: " << tracks.size() << std::endl;
}

bool 
osmio::open(std::string filename) {
 
  LIBXML_TEST_VERSION

  streamFile(filename.c_str());
 
  xmlCleanupParser();

  xmlMemoryDump();    

  return true;
};

void 
osmio::project(int level) {

  projected_tracks.clear();
  refined_tracks.clear();
  encoded_tracks.clear();

  vec2i min = mercatorProjection::getTile(north_west, level);
  vec2i max = mercatorProjection::getTile(south_east, level);

  // pass all tracks and project the location using mercator projection to 2d
  for (int t = 0; t != tracks.size(); t++) {
    track<geoLocation> curr_track = tracks[t];
    track<vec2i> proj_track;

    for (int n = 0; n < curr_track.size(); n++) {
      vec2i proj_loci = mercatorProjection::getTile(curr_track[n], level);

      if (proj_track.size() == 0 || proj_loci != proj_track[proj_track.size() - 1]) {
        proj_track.addCoordinate(proj_loci);
      }
    }
    projected_tracks.push_back(proj_track);
  }
}

void 
osmio::refine() {
  
  for (int t = 0; t != projected_tracks.size(); t++) {
    track<vec2i> curr_track = projected_tracks[t];
    track<vec2i> new_track;
    int n = 0;
    for (; n < (curr_track.size() - 1); n++) {
      vec2i diff = curr_track[n] - curr_track[n + 1];
      int steps = abs(diff[0]) > abs(diff[1]) ? diff[0] : diff[1];
      if (abs(steps) > 1) {
        vec2i c1 = curr_track[n];
        vec2i c2 = curr_track[n + 1];

        // BRESENHAM algorithm implementation
        bool steep = abs(c2.y - c1.y) > abs(c2.x - c1.x);
        bool change12 = false;

        if (steep) {
          SWAP(c1.x, c1.y);
          SWAP(c2.x, c2.y);
        }

        if (c1.x > c2.x) change12 = true;

        int deltax = abs(c2.x - c1.x);
        int deltay = abs(c2.y - c1.y);
        int error = deltax / 2;

        int ystep;
        int xstep;
        int y = c1.y;
        if (c1.y < c2.y) ystep = 1; else ystep = -1;
        if (c1.x < c2.x) xstep = 1; else xstep = -1;

        for (int x = c1.x; change12 ? x >= c2.x : x <= c2.x; x += xstep) {
          vec2i c12;
          if (steep) {
            c12.x = y;
            c12.y = x;
            new_track.addCoordinate(c12);
          }
          else {
            c12.x = x;
            c12.y = y;
            new_track.addCoordinate(c12);
          }
          error = error - deltay;
          if (error < 0) {
            y += ystep;
            error += deltax;
          }
        }
        // erase the last item because it will be added to track in next step
        new_track.pop_last();
      }
      else {
        new_track.addCoordinate(curr_track[n]);
      }
    }
    new_track.addCoordinate(curr_track[n]);
    refined_tracks.push_back(new_track);
  }
}

void 
osmio::encode() {

  for (int t = 0; t != refined_tracks.size(); t++) {
    track<vec2i> curr_track = refined_tracks[t];
    track<unsigned char> new_track;

    int n = 0;
    for (; n < (curr_track.size() - 1); n++) {
      vec2i diff = curr_track[n] - curr_track[n + 1];
      int steps = abs(diff[0]) > abs(diff[1]) ? diff[0] : diff[1];
      if (abs(steps) > 1) {
        cerr << "Detected step longer than 1, exiting ..." << endl;
        exit (1);
      }

      new_track.addCoordinate(chainCoder::encode(diff));
    }
    encoded_tracks.push_back(new_track);
  }
}

std::vector<track<geoLocation> > &
osmio::getTracks() {
  return tracks;
}

std::vector<track<vec2i> > &
osmio::getProjectedTracks() {
  return projected_tracks;
}

std::vector<track<vec2i> > &
osmio::getRefinedTracks() {
  return refined_tracks;
}

std::vector<track<unsigned char> > &
osmio::getEncodedTracks() {
  return encoded_tracks;
}

geoLocation
osmio::getNW() {
  return north_west;
}

geoLocation
osmio::getSE() {
  return south_east;
}

osmio::osmio() {
}

osmio::~osmio() {
}
